#include <stdio.h>
#include "nvs_flash.h"
#include "nvs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/gpio.h"

#define STORAGE_NAMESPACE "storage"



void nvs_init(void)
{
    //ESP_ERROR_CHECK(nvs_flash_init());
    esp_err_t err;
    err = nvs_flash_init();
    if(err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND )
    {
        // NVS 分区被截断，需要擦除
        // 重试 nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }

}

/* 在 NVS 中保存模块重启次数
   首先读取并递增
   之前保存的次数。
   如果在此过程中出现任何错误
   则返回错误信息。
 */
esp_err_t save_restart_count(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;

    //open
    err = nvs_open(STORAGE_NAMESPACE,NVS_READWRITE,&my_handle);
    if(err != ESP_OK) return err;

    //read
     int32_t restart_count = 0;
     err = nvs_get_i32 (my_handle,"restart_count", &restart_count);
     if(err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;

     //write
     restart_count++;
     err = nvs_set_i32(my_handle,"restart_count",restart_count);
     if (err != ESP_OK) return err;

    // 提交已写入的值。
    // 设置任何值后，必须调用 nvs_commit()，以确保更改被写入
    // 写入闪存。实现可能会在其他时间写入存储、
    // 但这并不保证。
     err = nvs_commit(my_handle);
    if (err != ESP_OK) return err;

    // Close
    nvs_close(my_handle);
    return ESP_OK;

}


/*在 NVS 中保存新的运行时间
   首先读取先前保存的值表
   然后在表格末尾添加新值。
   如果在此过程中出现任何错误
   则返回错误信息。
 */
esp_err_t save_run_time(void)
{
    //创建句柄
    nvs_handle_t my_handle;
    //创建用来检测错误的变量
     esp_err_t err;

     //open
     err = nvs_open(STORAGE_NAMESPACE,NVS_READWRITE,&my_handle);
     if(err != ESP_OK) return err;

     //read
     size_t runtime_size = 0; //记录运行时间的数组中的元素个数
      err = nvs_get_blob(my_handle, "run_time", NULL, &runtime_size);//获取runtime_size个数
      if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;

      uint32_t* run_time = malloc(runtime_size + sizeof(uint32_t));//申请一个大小为runtime_size+sizeof(uint32_t)的空间

     if(runtime_size > 0)
     {
        err = nvs_get_blob(my_handle, "run_time", run_time, &runtime_size);
        if(err != ESP_OK) 
        {
            free(run_time);
            return err; 
        }
     }

     //写入值，包括先前保存的 Blob（如果有的话
     runtime_size += sizeof(uint32_t);     
     //required_size / sizeof(uint32_t) 计算出当前 run_time 数组中元素的个数
     //xTaskGetTickCount() 获取当前系统的任务计数，并乘以 portTICK_PERIOD_MS 来转换为毫秒。
     run_time[runtime_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;
     err = nvs_set_blob(my_handle, "run_time", run_time, runtime_size);
     free(run_time);
     if (err != ESP_OK) return err;
     // Commit
     err = nvs_commit(my_handle);
     if (err != ESP_OK) return err;
     // Close
     nvs_close(my_handle);
     return ESP_OK;
}


/* Read from NVS and print restart counter
   and the table with run times.
   Return an error if anything goes wrong
   during this process.
   从 NVS 读取并打印重启计数器
   和包含运行时间的表格。
   如果在此过程中出现任何错误
   则返回错误信息。
 */
esp_err_t print_what_saved(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;
    size_t runtime_size = 0; 
    // Open
    err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
    if (err != ESP_OK) return err;

    // Read restart counter
    int32_t restart_count = 0; // value will default to 0, if not set yet in NVS
    err = nvs_get_i32(my_handle, "restart_count", &restart_count);
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
    printf("Restart counter = %ld\n", restart_count);

    // Read run time blob
     printf("Run time:\n");
     err = nvs_get_blob(my_handle, "run_time", NULL, &runtime_size);
    if (runtime_size == 0) {
        printf("Nothing saved yet!\n");
    } else {
        uint32_t* run_time = malloc(runtime_size);
        err = nvs_get_blob(my_handle, "run_time", run_time, &runtime_size);
        if (err != ESP_OK) {
            free(run_time);
            return err;
        }
        for (int i = 0; i < runtime_size / sizeof(uint32_t); i++) {
            printf("%d: %ld\n", i + 1, run_time[i]);
        }
        free(run_time);
    }

    //因为没有向nvs写入数据所以不需要调用commit
    // Close
    nvs_close(my_handle);
    return ESP_OK;
}

void mynvs_task(void *arg)
{
    #if CONFIG_IDF_TARGET_ESP32C3
    #define BOOT_MODE_PIN GPIO_NUM_9
    #else
    #define BOOT_MODE_PIN GPIO_NUM_0
    #endif 
    esp_err_t err;
    err = print_what_saved();
    if (err != ESP_OK) printf("Error (%s) reading data from NVS!\n", esp_err_to_name(err));

    err = save_restart_count();
    if (err != ESP_OK) printf("Error (%s) saving restart counter to NVS!\n", esp_err_to_name(err));

    gpio_reset_pin(BOOT_MODE_PIN);
    gpio_set_direction(BOOT_MODE_PIN, GPIO_MODE_INPUT);

    /* Read the status of GPIO0. If GPIO0 is LOW for longer than 1000 ms,
       then save module's run time and restart it
       读取 GPIO0 的状态。如果 GPIO0 低电平的时间超过 1000 ms、
       则保存模块运行时间并重启模块
     */
    while (1) {
        if (gpio_get_level(BOOT_MODE_PIN) == 0) {
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            if(gpio_get_level(BOOT_MODE_PIN) == 0) {
                err = save_run_time();
                if (err != ESP_OK) printf("Error (%s) saving run time blob to NVS!\n", esp_err_to_name(err));
                printf("Restarting...\n");
                fflush(stdout);         //将缓冲区中的输出立即刷新到终端，而不是等到缓冲区满或者程序结束时才刷新。
                esp_restart();
            }
        }
        vTaskDelay(200 / portTICK_PERIOD_MS);
    }
}